package com.luhuiguo.cms.server.webdav;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.vfs.AllFileSelector;
import org.apache.commons.vfs.FileContent;
import org.apache.commons.vfs.FileObject;
import org.apache.commons.vfs.FileSystemException;
import org.apache.commons.vfs.FileType;
import org.apache.commons.vfs.util.RandomAccessMode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.bradmcevoy.http.Auth;
import com.bradmcevoy.http.CollectionResource;
import com.bradmcevoy.http.FolderResource;
import com.bradmcevoy.http.Range;
import com.bradmcevoy.http.Request;
import com.bradmcevoy.http.Resource;
import com.bradmcevoy.http.exceptions.ConflictException;
import com.bradmcevoy.http.exceptions.NotAuthorizedException;

import eu.medsea.mimeutil.MimeType;
import eu.medsea.mimeutil.MimeUtil;

/**
 * 
 * @author LuHuiguo
 * @version $Id: GaeDirectoryResource.java 176 2010-05-17 09:22:49Z
 *          luhuiguo@gmail.com $
 */
public class GaeDirectoryResource extends GaeResource implements FolderResource {

    private static final Logger logger = LoggerFactory.getLogger(GaeDirectoryResource.class);

    public GaeDirectoryResource(String host, GaeResourceFactory factory, FileObject fileObject) {
        super(host, factory, fileObject);
    }

    @Override
    public String getContentType(String accepts) {
        if (accepts != null && accepts.contains("text/html"))
            return "text/html";
        return MimeUtil.UNKNOWN_MIME_TYPE.toString();
    }

    @Override
    public Long getContentLength() {
        return null;
    }

    @Override
    public Long getMaxAgeSeconds(Auth auth) {
        return null;
    }

    @Override
    public String checkRedirect(Request request) {
        if (factory.getDefaultPage() != null) {
            return request.getAbsoluteUrl() + "/" + factory.getDefaultPage();
        } else {
            return null;
        }
    }

    /**
     * Manually generates a standard HTML directory listing suitable for
     * standard filesystem web browsing.
     */
    private String directoryListing(FileObject fileObject) throws FileSystemException {
        // final String cacheHash = "#" + fileObject.hashCode();
        // final Object cached = GaeContext.getCached(cacheHash);
        // if (cached != null) {
        // return (String) cached;
        // }

        StringWriter writer = new StringWriter();

        writer.write("<html><head><meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\" />");
        writer.write("<title>Index of " + getPath(fileObject) + "</title></head>");
        writer.write("<body bgcolor=\"white\">\n");
        writer.write("<h1>Index of " + getPath(fileObject) + "</h1><hr><pre>\n");
        if (fileObject.getParent() != null) {
            writer.write("<a href=\"" + getPath(fileObject.getParent()) + "\">../</a>\n");
        }
        final FileObject[] files = fileObject.getChildren();
        for (FileObject file : files) {
            if (file.exists()) {
                final boolean isFolder = file.getType().equals(FileType.FOLDER);
                String path = file.getName().getBaseName() + (isFolder ? "/" : "");
                path = StringUtils.abbreviate(path, 30);
                writer.write("<a href=\"" + getPath(file) + "\">" + path + "</a>");
                writer.write(StringUtils.rightPad("", 30 - path.length()));

                final String date = new Date(file.getContent().getLastModifiedTime()).toString();
                writer.write(date);
                writer.write(StringUtils.rightPad("", 30 - date.length()));
                writer.write(isFolder ? "-" : (String.valueOf(file.getContent().getSize())));
                writer.write("\n");
            }
        }
        writer.write("</pre><hr>");
        writer.write(new Date().toString());
        writer.write("</body></html>");
        final String s = writer.toString();
        // GaeContext.putCached(cacheHash, s, 1000 * 60 * 5);
        return s;
    }

    private String getPath() {
        return getPath(fileObject);
    }

    /**
     * Returns a relative path segment NOT beginning with "/".
     */
    private String getPath(FileObject file) {

        try {
            String s = file.getName().getPathDecoded();
            if (s.startsWith("/")) {
                s = s.substring(1);
            }
            s = "/" + factory.getContextPath() + "/" + s;
            return s;
        } catch (FileSystemException e) {
            logger.warn(e.getMessage(), e);
            // e.printStackTrace();
            return "";
        }
    }

    @Override
    public void sendContent(OutputStream out, Range range, Map<String, String> params, String contentType)
            throws IOException, NotAuthorizedException {
        // Manually construct a simple directory listing
        logger.info("Sending directory content: {},range={}", contentType, range);
        if (contentType == null || !contentType.equals("text/html")) {
            out.close();
            return;
        }
        logger.info("Sending directory listing");

        byte[] directoryListing = directoryListing(fileObject).getBytes();
        out.write(directoryListing);
        out.close();
        // Allow listing to be refreshed next time
        // GaeContext.expireCached("#" + fileObject.hashCode());
    }

    /**
     * @return null if the specified child does not exist.
     */
    @Override
    public Resource child(String childName) {
        try {
            final FileObject child = fileObject.getChild(childName);
            return (child == null || !child.exists()) ? null : new GaeFileResource(host, factory, child);
        } catch (FileSystemException e) {
            logger.warn(e.getMessage(), e);
            // e.printStackTrace();
            return null;
        }
    }

    @Override
    public List<? extends Resource> getChildren() {
        List<Resource> children = new ArrayList<Resource>();
        try {
            for (FileObject child : fileObject.getChildren()) {
                if (child.exists()) {
                    children.add((child.getType() == FileType.FOLDER) ? new GaeDirectoryResource(host, factory, child)
                            : new GaeFileResource(host, factory, child));
                }
            }
        } catch (FileSystemException e) {
            logger.warn(e.getMessage(), e);
            // e.printStackTrace();
        }
        return children;
    }

    @Override
    public Resource createNew(String newName, InputStream inputStream, Long length, String contentType)
            throws IOException {
        try {
            if (length == null) {
                length = 0L;
            }
            logger.info("Create new file in {}! name={}, contentType={}",
                    new Object[]{ getPath(), newName, contentType });

            final FileObject newFile = fileObject.resolveFile(newName);
            if (contentType != null && contentType.equals(MimeUtil.DIRECTORY_MIME_TYPE.toString())) {
                newFile.createFolder();
                return new GaeDirectoryResource(host, factory, newFile);
            }
            newFile.createFile();
            final OutputStream out = newFile.getContent().getOutputStream();
            final int i = IOUtils.copy(inputStream, out);
            logger.info("Transferred {} bytes!", i);
            if (i != length) {
                throw new IOException("Specified length does not match file upload");
            }
            out.close();
            inputStream.close();
            setMimeType(contentType == null ? MimeUtil.UNKNOWN_MIME_TYPE.toString() : contentType, newFile);
            return new GaeFileResource(host, factory, newFile);
        } catch (IOException e) {
            logger.warn(e.getMessage(), e);
            // e.printStackTrace();
            return null;
        }
    }

    @SuppressWarnings("unchecked")
    private void setMimeType(String mimeType, FileObject fileObject) throws FileSystemException {
        final String finalType;
        final FileContent content = fileObject.getContent();
        if (!MimeUtil.UNKNOWN_MIME_TYPE.toString().equals(mimeType)) {

            logger.debug("Client passed mime type: {}", mimeType);

            final String[] types = mimeType.split(",");
            Collection<MimeType> typeList = new ArrayList<MimeType>();
            for (String type : types) {
                typeList.add(new MimeType(type));
            }
            finalType = MimeUtil.getMostSpecificMimeType(typeList).toString();
        } else {
            final Collection<MimeType> types = MimeUtil.getMimeTypes(getName());

            logger.debug("name types={}", types);

            try {
                InputStream in = content.getRandomAccessContent(RandomAccessMode.READ).getInputStream();
                final Collection<MimeType> types2 = MimeUtil.getMimeTypes(in);

                logger.debug("stream types={}", types2);

                types.addAll(types2);
                in.close();
            } catch (Exception e) {
                logger.warn(e.getMessage(), e);
                // e.printStackTrace();
            }
            if (types.size() > 1) {
                types.remove(MimeUtil.UNKNOWN_MIME_TYPE);
            }
            finalType = MimeUtil.getMostSpecificMimeType(types).toString();
            logger.debug("Deriving mime type from file, final type= {}", finalType);
        }
        logger.debug("Setting MIME type to {}", finalType);
        content.setAttribute("mime-type", finalType);
    }

    @Override
    public CollectionResource createCollection(String newName) throws NotAuthorizedException, ConflictException {
        try {

            logger.debug("Creating new folder in {}, name={}", getPath(), newName);

            final FileObject newFolder = fileObject.resolveFile(newName);
            if (newFolder.exists()) {
                return null;
            }
            newFolder.createFolder();
            return new GaeDirectoryResource(host, factory, newFolder);
        } catch (FileSystemException e) {
            logger.warn(e.getMessage(), e);
            // e.printStackTrace();
            return null;
        }
    }

    @Override
    public void moveTo(CollectionResource rDest, String name) throws ConflictException {
        logger.debug("Moving file! dest={}, name={}", rDest.getName(), name);
        try {
            if (rDest instanceof GaeDirectoryResource) {
                final FileObject destFile = ((GaeDirectoryResource) rDest).fileObject.resolveFile(name);
                if (!fileObject.canRenameTo(destFile)) {
                    throw new ConflictException(rDest.child(name));
                }
                destFile.createFolder();
                destFile.copyFrom(fileObject, new AllFileSelector());
                fileObject.delete(new AllFileSelector());
            } else {
                throw new IOException("Wrong resource type");
            }
        } catch (IOException e) {
            logger.warn(e.getMessage(), e);
            // e.printStackTrace();
        }
    }

    @Override
    public void copyTo(CollectionResource toCollection, String name) {

        logger.debug("Copying file! dest={}, name={}", toCollection.getName(), name);

        try {
            if (toCollection instanceof GaeDirectoryResource) {
                final FileObject destFile = ((GaeDirectoryResource) toCollection).fileObject.resolveFile(name);
                if (destFile.exists()) {
                    destFile.delete(new AllFileSelector());
                }
                destFile.createFolder();
                destFile.copyFrom(fileObject, new AllFileSelector());
            } else {
                throw new IOException("Wrong resource type");
            }
        } catch (IOException e) {
            logger.warn(e.getMessage(), e);
            // e.printStackTrace();
        }
    }
}
